package pl.com.qapps.datastore.berkeley;

import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;

import pl.com.qapps.datastore.base.MultiQueryBuilder;
import pl.com.qapps.datastore.base.PreparedMultiQuery;
import pl.com.qapps.datastore.base.PreparedQueryImpl;
import pl.com.qapps.datastore.base.QueryRunner;
import pl.com.qapps.datastore.base.QuerySplitHelper;
import pl.com.qapps.datastore.berkeley.impl.TransactionImpl;


import com.google.appengine.api.datastore.BaseDatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
import com.google.appengine.api.datastore.ImplicitTransactionManagementPolicy;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.TransactionOptions;
import com.google.apphosting.api.ApiProxy.ApiConfig;
import com.sleepycat.collections.CurrentTransaction;
import com.sleepycat.collections.TransactionRunner;
import com.sleepycat.collections.TransactionWorker;
import com.sleepycat.je.TransactionConfig;

/**
 * 
 * @author Jakub Dykowski
 *
 */
abstract class BaseBerkeleyDatastoreService implements BaseDatastoreService {

//	final TransactionStack txnProvider;
	final FileStore store;
	final ExecutorService dispatcher;
//	private final TransactionRunner runner;
	private final CurrentTransaction currentTxn;
	private final QueryRunner queryRunner;

	private final DatastoreServiceConfig datastoreServiceConfig;

	BaseBerkeleyDatastoreService(DatastoreServiceConfig cfg, FileStore store,
			TransactionStack stack, ExecutorService dispatcher) {
		this.datastoreServiceConfig = cfg;
		this.store = store;
//		this.txnProvider = stack;
		this.dispatcher = dispatcher;
//		this.runner = new TransactionRunner(store.getEnvironment());
		this.currentTxn = CurrentTransaction
				.getInstance(store.getEnvironment());
		this.queryRunner = new QueryRunner(store, dispatcher);
	}

	/**
	 * Helper class used to encapsulate the result of a call to
	 * {@link #getOrCreateTransaction()}.
	 */
	static final class GetOrCreateTransactionResult {

		private final boolean isNew;
		private final Transaction txn;

		GetOrCreateTransactionResult(boolean isNew, Transaction txn) {
			this.isNew = isNew;
			this.txn = txn;
		}

		/**
		 * @return {@code true} if the Transaction was created and should
		 *         therefore be closed before the end of the operation,
		 *         {@code false} otherwise.
		 */
		public boolean isNew() {
			return isNew;
		}

		/**
		 * @return The Transaction to use. Can be {@code null}.
		 */
		public Transaction getTransaction() {
			return txn;
		}
	}

	/**
	 * Return the current transaction if one already exists, otherwise create a
	 * new transaction or throw an exception according to the
	 * {@link ImplicitTransactionManagementPolicy}.
	 */
	GetOrCreateTransactionResult getOrCreateTransaction() {
		Transaction currentTxn = getCurrentTransaction(null);
		if (currentTxn != null) {
			return new GetOrCreateTransactionResult(false, currentTxn);
		}
		return new GetOrCreateTransactionResult(true,
				beginTransactionInternal(TransactionOptions.Builder
						.withDefaults()));
	}

	@Override
	public Collection<Transaction> getActiveTransactions() {
		return txnProvider.getAll();
	}

	@Override
	public Transaction getCurrentTransaction() {
		return txnProvider.peek();
	}

	@Override
	public Transaction getCurrentTransaction(Transaction defaultTxn) {
		return txnProvider.peek(defaultTxn);
	}

	@Override
	public PreparedQuery prepare(Query query) {
		return prepare(null, query);
	}

	@Override
	public PreparedQuery prepare(Transaction txn, Query query) {
		ApiConfig apiConfig = new ApiConfig();
		MultiQueryBuilder queriesToRun = QuerySplitHelper.splitQuery(query);
		if (queriesToRun != null) {
			System.out.println("spliting into multiple queries");
			return new PreparedMultiQuery(apiConfig, datastoreServiceConfig,
					queriesToRun, txn, queryRunner);
		}
		System.out.println("running single query");
		return new PreparedQueryImpl(apiConfig, datastoreServiceConfig, query,
				txn, queryRunner);
	}

	private com.sleepycat.je.Transaction beginRawTransaction() {
		return beginRawTransaction(null);
	}

	private com.sleepycat.je.Transaction beginRawTransaction(
			TransactionConfig txnConfig) {
		return store.getEnvironment().beginTransaction(null,
				txnConfig != null ? txnConfig : TransactionConfig.DEFAULT);
	}

	TransactionImpl beginTransactionInternal() {
		return beginTransactionInternal(null);
	}

	TransactionImpl beginTransactionInternal(TransactionOptions options) {
		return new TransactionImpl(txnProvider, beginRawTransaction(),
				dispatcher);
	}

	static void verifyTxn(Transaction txn) {
		if (txn != null && !(txn instanceof TransactionImpl))
			throw new IllegalArgumentException(
					"Most likely implementation error, given txn is not of TransactionImpl type");
	}

	abstract class TxnRunner<T> implements Callable<T> {

		private Transaction txn;

		public TxnRunner(Transaction txn) {
			verifyTxn(txn);
			TransactionImpl.ensureTxnActive(txn);
			this.txn = txn;
		}

		abstract T runInternal(com.sleepycat.je.Transaction txn);

		@Override
		final public T call() throws Exception {
			if (txn == null) {
				// begin and finish new transaction here
				ResultWorker worker = new ResultWorker();
				runner.run(worker);
				return worker.result;
			} else {
				// use existing transaction and do not finish it
				TransactionImpl.ensureTxnActive(txn);
				return runInternal(((TransactionImpl) txn).raw);
			}
		}

		private class ResultWorker implements TransactionWorker {

			T result;

			@Override
			public void doWork() throws Exception {
				result = runInternal(currentTxn.getTransaction());
			}
		}
	}

}
